package com.github.common.util.lang;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 日期工具类
 */
public class LocalDateUtil {

    /**
     * 日期格式：yyyy-MM-dd HH:mm:ss
     */
    public static final DateTimeFormatter YMD_HMS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static final String PATTERN_YYYYMM = "yyyyMM";

    /**
     * 比较两个日期大小
     *
     * @param d1 时间1
     * @param d2 时间2
     * @return trued：d1>d2   false：d1<d2
     */
    public boolean before(Date d1, Date d2) {
        return d1.before(d2);
    }

    /**
     * 获取当前时间
     *
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String now() {
        return now(YMD_HMS);
    }

    /**
     * 获取当前时间
     *
     * @param formatter 日期格式化格式
     * @return 格式化后的日期
     */
    public static String now(DateTimeFormatter formatter) {
        return LocalDateTime.now().format(formatter);
    }


    /**
     * 获取当前时间
     *
     * @return {@link Date}对象
     */
    public static Date nowOfDate() {
        return toDate(LocalDateTime.now());
    }

    /**
     * 当前时间减去N秒
     *
     * @param seconds 所要减去的秒数
     * @return {@link Date}对象
     */
    public static Date minusSecondsOfDate(long seconds) {
        LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(seconds);
        return toDate(localDateTime);
    }

    /**
     * 当前时间减去N秒
     *
     * @param seconds 所要减去的秒数
     * @return 格式化后的日期
     */
    public static String minusSeconds(long seconds) {
        return minusSeconds(seconds, YMD_HMS);
    }

    /**
     * 当前时间减去N秒
     *
     * @param seconds   所要减去的秒数
     * @param formatter 日期格式化格式
     * @return 格式化后的日期
     */
    public static String minusSeconds(long seconds, DateTimeFormatter formatter) {
        return LocalDateTime.now().minusSeconds(seconds).format(formatter);
    }

    /**
     * 当前时间增加N秒
     *
     * @param seconds 所要增加的秒数
     * @return {@link Date}对象
     */
    public static Date plusSecondsOfDate(long seconds) {
        LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(seconds);
        return toDate(localDateTime);
    }

    /**
     * 当前时间增加N秒
     *
     * @param seconds 所要增加的秒数
     * @return 格式化后的日期
     */
    public static String plusSeconds(long seconds) {
        return plusSeconds(seconds, YMD_HMS);
    }

    /**
     * 当前时间增加N秒
     *
     * @param seconds   所要增加的秒数
     * @param formatter 日期格式化格式
     * @return 格式化后的日期
     */
    public static String plusSeconds(long seconds, DateTimeFormatter formatter) {
        return LocalDateTime.now().plusSeconds(seconds).format(formatter);
    }

    /**
     * 当前时间增加N分钟
     *
     * @param minutes 所要增加的分钟数
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String minusMinutes(long minutes) {
        return minusMinutes(minutes, YMD_HMS);
    }

    /**
     * 当前时间增加N分钟
     *
     * @param minutes   所要增加的分钟数
     * @param formatter 日期格式化格式
     * @return 格式化后的日期
     */
    public static String minusMinutes(long minutes, DateTimeFormatter formatter) {
        return LocalDateTime.now().minusMinutes(minutes).format(formatter);
    }

    /**
     * 当前时间减去N分钟
     *
     * @param minutes 所要减去的分钟数
     * @return {@link Date}对象
     */
    public static Date minusMinutesOfDate(long minutes) {
        LocalDateTime localDateTime = LocalDateTime.now().minusMinutes(minutes);
        return toDate(localDateTime);
    }

    /**
     * 当前时间增加N分钟
     *
     * @param minutes 所要增加的分钟数
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String plusMinutes(long minutes) {
        return plusMinutes(minutes, YMD_HMS);
    }

    /**
     * 当前时间增加N分钟
     *
     * @param minutes   所要增加的分钟数
     * @param formatter 日期格式化格式
     * @return 格式化后的日期
     */
    public static String plusMinutes(long minutes, DateTimeFormatter formatter) {
        return LocalDateTime.now().plusMinutes(minutes).format(formatter);
    }

    /**
     * 当前时间增加N分钟
     *
     * @param minutes 所要增加的分钟数
     * @return {@link Date}对象
     */
    public static Date plusMinutesOfDate(long minutes) {
        LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(minutes);
        return toDate(localDateTime);
    }

    /**
     * 转换为日期
     *
     * @param localDateTime
     * @return
     */
    private static Date toDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 当前时间减去N小时
     *
     * @param hours 所要减去的小时
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String minusHours(long hours) {
        return minusHours(hours, YMD_HMS);
    }

    /**
     * 当前时间减去N小时
     *
     * @param hours     所要减去的小时
     * @param formatter 日期格式化格式
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String minusHours(long hours, DateTimeFormatter formatter) {
        return LocalDateTime.now().minusHours(hours).format(formatter);
    }

    /**
     * 当前时间减去N小时
     *
     * @param hours 所要减去的小时
     * @return 格式化后的日期
     */
    public static Date minusHoursOfDate(long hours) {
        return toDate(LocalDateTime.now().minusHours(hours));
    }

    /**
     * 当前时间增加N小时
     *
     * @param hours     所要增加的小时
     * @param formatter 日期格式化格式
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String plusHours(long hours, DateTimeFormatter formatter) {
        return LocalDateTime.now().plusHours(hours).format(formatter);
    }


    /**
     * 当前时间增加N小时
     *
     * @param hours 所要增加的小时
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String plusHours(long hours) {
        return plusHours(hours, YMD_HMS);
    }

    /**
     * 当前时间增加N小时
     *
     * @param hours 所要增加的小时
     * @return 格式化后的日期
     */
    public static Date plusHoursOfDate(long hours) {
        return toDate(LocalDateTime.now().plusHours(hours));
    }


    /**
     * 当前时间减去N天
     *
     * @param days 所要减去的天数
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String minusDays(long days) {
        return minusDays(days, YMD_HMS);
    }

    /**
     * 当前时间减去N天
     *
     * @param days      所要减去的天数
     * @param formatter 日期格式化格式
     * @return 格式化后的日期
     */
    public static String minusDays(long days, DateTimeFormatter formatter) {
        return LocalDateTime.now().minusDays(days).format(formatter);
    }

    /**
     * 当前时间减去N天
     *
     * @param days 所要减去的天数
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static Date minusDaysOfDate(long days) {
        return toDate(LocalDateTime.now().minusDays(days));
    }

    /**
     * 当前时间增加N天
     *
     * @param days 所要增加的天数
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String plusDays(long days) {
        return plusDays(days, YMD_HMS);
    }

    /**
     * 当前时间增加N天
     *
     * @param days      所要增加的天数
     * @param formatter 日期格式化格式
     * @return 格式化后的日期
     */
    public static String plusDays(long days, DateTimeFormatter formatter) {
        return LocalDateTime.now().plusDays(days).format(formatter);
    }

    /**
     * 当前时间增加N天
     *
     * @param days 所要增加的天数
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static Date plusDaysOfDate(long days) {
        return toDate(LocalDateTime.now().plusDays(days));
    }

    /**
     * 将时间戳(毫秒值) 转换为日期字符串(格式:yyyy-MM-dd HH:mm:ss)
     *
     * @param timeStamp 毫秒值
     * @return
     */
    public static String formatMilliToDateStr(long timeStamp) {
        return formatMilliToDateStr(timeStamp, YMD_HMS);
    }

    /**
     * 将时间戳(毫秒值) 转换为日期字符串(格式:yyyy-MM-dd HH:mm:ss)
     *
     * @param timeStamp 毫秒值
     * @param formatter 日期格式化格式
     * @return
     */
    public static String formatMilliToDateStr(long timeStamp, DateTimeFormatter formatter) {
        Instant instant = Instant.ofEpochMilli(timeStamp);
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return localDateTime.format(formatter);
    }

    /**
     * 将日期字符串转换为日期对象
     *
     * @param dateStr 日期字符串(格式:yyyy-MM-dd HH:mm:ss)
     * @return
     */
    public static Date parse(String dateStr) {
        return parse(dateStr, YMD_HMS);
    }

    /**
     * 将日期字符串转换为日期对象
     *
     * @param dateStr   日期字符串(格式:yyyy-MM-dd HH:mm:ss)
     * @param formatter 日期格式化格式
     * @return
     */
    public static Date parse(String dateStr, DateTimeFormatter formatter) {
        //得到时间戳对象
        Instant instant = LocalDateTime.parse(dateStr, formatter).atZone(ZoneId.systemDefault()).toInstant();
        //转换为日期
        return Date.from(instant);
    }

    /**
     * 将日期字符串转换为时间戳(毫秒)
     *
     * @param dateStr 日期字符串(格式:yyyy-MM-dd HH:mm:ss)
     * @return
     */
    public static long timeStamp(String dateStr) {
        //日期字符串
        return LocalDateTime.parse(dateStr, YMD_HMS).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    /**
     * 将日期字符串转换为时间戳(毫秒)
     *
     * @param dateStr 日期字符串(格式:yyyy-MM-dd HH:mm:ss)
     * @return
     */
    public static long timeStamp(String dateStr, String dateFormat) {
        if (StringUtil.isEmpty(dateStr)) {
            return 0L;
        }

        //日期字符串
        return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(dateFormat)).atZone(ZoneId.systemDefault())
                .toInstant().toEpochMilli();
    }


    public static String getNowYearMonth(String pattern) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return LocalDate.now().format(formatter);
    }

    public static String getNowYearMonth() {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN_YYYYMM);
        return LocalDate.now().format(formatter);
    }

    /**
     * 获取两个月份之间的所有月份
     *
     * @param startDate
     * @param endDate
     * @return
     */
    public static Collection<String> getMonthBetween(String startDate, String endDate, String datePattern) {
        ArrayList result = new ArrayList();
        Calendar startCalendar = parse(startDate, datePattern);
        Calendar endCalendar = parse(endDate, datePattern);

        startCalendar.set(startCalendar.get(Calendar.YEAR), startCalendar.get(Calendar.MONTH), 1);
        endCalendar.set(endCalendar.get(Calendar.YEAR), endCalendar.get(Calendar.MONTH), 2);

        if (startCalendar.getTimeInMillis() > endCalendar.getTimeInMillis()) {
            throw new IllegalArgumentException("end date should not be before start date");
        }

        Calendar tmpCalendar = startCalendar;
        while (tmpCalendar.before(endCalendar)) {
            result.add(new SimpleDateFormat(datePattern).format(tmpCalendar.getTime()));
            tmpCalendar.add(Calendar.MONTH, 1);
        }
        return result;
    }

    /**
     * 解析日期
     *
     * @param date   日期
     * @param format 格式
     * @return 解析后的日期
     */
    public static Calendar parse(String date, String format) {
        AssertUtil.assertNotEmpty(date, "date");

        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
        Calendar calendar = Calendar.getInstance();
        try {
            calendar.setTime(dateFormat.parse(date));
        } catch (ParseException e) {
            throw new IllegalArgumentException("Illegal argument:" + date, e);
        }
        return calendar;
    }

    /**
     * 获取两个日期的差值
     *
     * @param source
     * @param target
     * @param timeUnit
     * @return
     */
    private static long doBetween(LocalDateTime source, LocalDateTime target, TimeUnit timeUnit) {
        Duration duration = Duration.between(source, target);

        switch (timeUnit) {
            case DAYS:
                return Period.between(source.toLocalDate(), target.toLocalDate()).getDays();
            case HOURS:
                return duration.toHours();
            case MINUTES:
                return duration.toMinutes();
            case MILLISECONDS:
                return duration.toMillis();
            case SECONDS:
                return duration.toMillis() / 1000;
            case NANOSECONDS:
                return duration.toNanos();
            case MICROSECONDS:
                return duration.toMillis() * 1000;
            default:
                throw new RuntimeException("unknown timeUnit:" + timeUnit.name());
        }
    }
}
